#include "Avalon/Threading/Async.hpp"

using namespace Avalon;
using namespace Avalon::Threading;
using namespace boost;
using namespace boost::posix_time;
using namespace std;

Async<void>::Async()
    : m_state(PENDING) {}

Async<void>::Async(bool dummy)
    : m_state(COMPLETE) {}

Async<void>::~Async() {}

void Async<void>::SetResult() {
  unique_lock<mutex> lock(m_mutex);
  assert(m_state == PENDING);
  SetState(COMPLETE, lock);
}

const exception_ptr& Async<void>::GetException() const {
  assert(m_state == EXCEPTION);
  return m_exception;
}

void Async<void>::SetException(const exception_ptr& e) {
  unique_lock<mutex> lock(m_mutex);
  assert(m_state == PENDING);
  m_exception = e;
  SetState(EXCEPTION, lock);
}

void Async<void>::ResetResult() {
  if(m_state == PENDING) {
    return;
  }
  m_state = PENDING;
  m_exception.reset();
}

void Async<void>::Reset() {
  if(m_state == PENDING) {
    return;
  }
  m_state = PENDING;
  m_exception.reset();
  m_finishedSlot.reset();
}

void Async<void>::Get() const {
  assert(m_state & (COMPLETE | EXCEPTION));
  if(m_state == EXCEPTION) {
    rethrow_exception(m_exception);
  }
}

void Async<void>::Get(time_duration timeout) const {
  if(Wait(timeout) == PENDING) {
    BOOST_THROW_EXCEPTION(TimeoutException());
  }
  return Get();
}

Async<void>::State Async<void>::GetState() const {
  assert(m_state != PENDING);
  return m_state;
}

Async<void>::State Async<void>::Wait(time_duration timeout) const {
  unique_lock<mutex> lock(m_mutex);
  if(m_state == PENDING) {
    if(timeout == pos_infin) {
      m_isFinishedCondition.wait(lock);
      assert(m_state != PENDING);
    } else {
      m_isFinishedCondition.timed_wait(lock, timeout);
    }
  }
  return m_state;
}

void Async<void>::SetFinishedSlot(const FinishedSlot& finishedSlot,
    Out<State> state) {
  lock_guard<mutex> lock(this->m_mutex);
  *state = this->m_state;
  if(this->m_state != PENDING) {
    return;
  }
  m_finishedSlot.reset(new FinishedSlot(finishedSlot));
}

void Async<void>::SetState(State state, unique_lock<mutex>& lock) {
  assert(this->m_state == PENDING);
  assert(state != PENDING);
  this->m_state = state;
  this->m_isFinishedCondition.notify_all();
  if(m_finishedSlot == NULL) {
    return;
  }
  boost::shared_ptr<FinishedSlot> finishedSlot = m_finishedSlot;
  lock.unlock();
  (*finishedSlot)();
}
